package com.dz.risk.custom.dynamic;

import com.dz.risk.common.SysConstant;
import com.dz.risk.common.enums.SystemLogEnum;
import com.dz.risk.common.utils.FutureUtils;
import com.dz.risk.constant.IndexConstant;
import com.dz.risk.exception.VerifyException;
import com.dz.risk.pojo.domain.ReportFormsDataValueEntity;
import com.dz.risk.pojo.domain.ReportFormsEntity;
import com.dz.risk.pojo.domain.SystemLogEntity;
import com.dz.risk.service.IndexDealWith;
import com.dz.risk.service.ReportFormsDataValueService;
import com.dz.risk.service.SystemLogService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.AsyncResult;
import org.springframework.stereotype.Component;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.DefaultTransactionDefinition;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;

import java.sql.Connection;
import java.text.NumberFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicLong;

/**
 * @author Doke
 * @date 2021/8/18 11:09
 */
@Slf4j
@Component
public class ReportFormsCalculation {

    private final ReportFormsDataValueService reportFormsDataValueService;

    private final DataSourceTransactionManager transactionManager;

    private final ApplicationContext applicationContext;

    private final SystemLogService systemLogService;

    @Autowired
    public ReportFormsCalculation(ReportFormsDataValueService reportFormsDataValueService, DataSourceTransactionManager transactionManager, ApplicationContext applicationContext, SystemLogService systemLogService) {
        this.reportFormsDataValueService = reportFormsDataValueService;
        this.transactionManager = transactionManager;
        this.applicationContext = applicationContext;
        this.systemLogService = systemLogService;
    }

    /**
     * 异步计算报表数据值
     *
     * @param taskCount            任务总数量
     * @param taskData             需要处理的任务数据集合
     * @param reportFormsEntity    报表基本信息
     * @param calculatingTime      计算时间
     * @param barrier              回环栅栏
     * @param processingDataLength 已经处理的数据量
     */
    @Async("customizeExecutor")
    public Future<Integer> reportFormsDataValueCalculation(final int taskCount, List<ReportFormsDataValueEntity> taskData, ReportFormsEntity reportFormsEntity, Date calculatingTime, CyclicBarrier barrier, AtomicLong processingDataLength) throws InterruptedException, TimeoutException, BrokenBarrierException {
        Assert.notNull(calculatingTime, "计算时间不能为空");
        Integer flag = 0;
        String errorMsg = null;

        //获取事务管理器
        DefaultTransactionDefinition definition = new DefaultTransactionDefinition();
        definition.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
        definition.setIsolationLevel(Connection.TRANSACTION_READ_UNCOMMITTED);
        TransactionStatus status = transactionManager.getTransaction(definition);

        //需要新增得计算编码数据集合
        List<ReportFormsDataValueEntity> reportFormsDataValueEntities = new ArrayList<>();

        try {
            //遍历数据集合并计算有公式的报表数据值
            taskData.forEach(a -> {
                //没有编号的单元格数据对象不进行计算
                if (!StringUtils.isEmpty(a.getTableCellSerialNumber())) {
                    Double dataValue = cellCalculation(a, reportFormsEntity.getBeginDate(), reportFormsEntity.getEndDate(), calculatingTime);
                    Optional.ofNullable(dataValue).ifPresent(v -> a.setDataValue(formatDouble(v)));
                }

                //如果单元格编号为混合运算,计算出结果时就已保存,此处无需放入待新增的集合保存数据
                if (StringUtils.isEmpty(a.getTableCellSerialNumber()) || !IndexConstant.MIXED_COMPUTING.equals(a.getTableCellSerialNumber().substring(a.getTableCellSerialNumber().length() - 1))) {
                    //设置计算版本
                    a.setVersion(calculatingTime);
                    reportFormsDataValueEntities.add(a);
                }
            });

            //保存报表计算数据集合
            reportFormsDataValueService.saveBatch(reportFormsDataValueEntities);
            //添加数据已处理量
            processingDataLength.getAndAdd(taskData.size());

        } catch (Exception e) {
            processingDataLength.getAndSet(-1);
            log.error(e.getMessage(), e);
            e.printStackTrace();
            errorMsg = e.getMessage();
        } finally {
            barrier.await(FutureUtils.TIME, TimeUnit.MINUTES);
            if (taskCount == processingDataLength.get()) {
                transactionManager.commit(status);
                flag = taskData.size();
            } else {
                transactionManager.rollback(status);
            }
            //如果有异常信息则记录日志信息
            checkRecord(errorMsg);

            //处理完后清除数据
            reportFormsDataValueEntities.clear();
        }
        return new AsyncResult<>(flag);
    }

    /**
     * 验证并记录日志
     *
     * @param errorMsg 错误信息
     */
    private void checkRecord(String errorMsg) {
        //判断是否有错误信息
        if (!StringUtils.isEmpty(errorMsg)) {
            systemLogService.addStSystemLog(SystemLogEntity
                    .builder()
                    .actionType(SystemLogEnum.ABNORMAL_CALCULATION.getActionType())
                    .operationContent(errorMsg)
                    .manipulationMenu(SysConstant.REPORT_MANAGEMENT)
                    .build());
        }
    }

    /**
     * 计算单元格的报表数据值</p>
     * 1.如果该单元格设置了公式则进行公式计算求值<tr/>
     * 2.如果该单元格未设置值则查询临时中间表中是否有该编号的此版本数据值<tr/>
     * 3.如果公式及中间表的值都未存在则取该单元格自身所设置的数据值<tr/>
     *
     * @param reportFormsDataValueEntity 报表单元格数据实体
     * @param beginDate                  报表开始时间
     * @param endDate                    报表结束时间
     * @param calculatingTime            计算版本
     * @return 单元格数据值
     */
    public Double cellCalculation(ReportFormsDataValueEntity reportFormsDataValueEntity, Date beginDate, Date endDate, Date calculatingTime) {
        //1.获取编码类型
        String indexType = getIndexType(reportFormsDataValueEntity.getTableCellSerialNumber());

        IndexDealWith indexDealWith;
        //2.根据编号获取该编号类型得编码处理类
        try {
            indexDealWith = applicationContext.getBean("IndexDealWith_" + indexType, IndexDealWith.class);
        } catch (Exception e) {
            throw new VerifyException(reportFormsDataValueEntity.getTableCellSerialNumber() + "编码类型不存在");
        }

        //3.获取编码对应得指标值
        return indexDealWith.getDataByReportFormsDataValueAndCalculatingTime(reportFormsDataValueEntity, calculatingTime, beginDate, endDate);
    }

    /**
     * 根据编码获取指标类型
     *
     * @param indexId 指标编码
     * @return 指标类型(0 混合运算, 1 上游数据, 2 人工导入, 3 sql运算)
     */
    private String getIndexType(String indexId) {
        return indexId.substring(indexId.length() - 1);
    }

    private static String formatDouble(double d) {
        NumberFormat nf = NumberFormat.getInstance();
        //设置保留多少位小数
        nf.setMaximumFractionDigits(4);
        // 取消科学计数法
        nf.setGroupingUsed(false);
        //返回结果
        return nf.format(d);
    }
}
